package sqlDB

import (
	"fmt"
	"gitee.com/tsingeye/FreeEhome/config"
	"gitee.com/tsingeye/FreeEhome/tools/logs"
	"gorm.io/driver/mysql"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"time"
)

//初始化数据表
func initTable() {
	timeNow := time.Now().Format(config.TimeLayout)

	//初始化用户信息表
	if !gormDB.Migrator().HasTable(&UserTable{}) {
		//创建用户信息表
		CreateTable(&UserTable{}, "用户信息表")

		var users = []UserTable{
			{
				UserID:    "admin",
				Username:  "admin",
				Password:  "21232f297a57a5a743894a0e4a801fc3",
				CreatedAt: timeNow,
				UpdatedAt: timeNow,
			},
			{
				UserID:    "guest",
				Username:  "guest",
				Password:  "084e0343a0486ff05530df6c705c8bb4",
				CreatedAt: timeNow,
				UpdatedAt: timeNow,
			},
		}

		if err := Create(&UserTable{}, &users, false); err != nil {
			logs.BeeLogger.Error("initTable() ---> Create(&UserTable{}) error:%s", err)
		} else {
			logs.BeeLogger.Notice("successful init default user in the %s table", GetTableName(&UserTable{}))
		}
	}

	//初始化设备信息表
	if !gormDB.Migrator().HasTable(&DeviceTable{}) {
		//创建设备信息表
		CreateTable(&DeviceTable{}, "设备信息表")
	}

	//初始化设备通道列表
	if !gormDB.Migrator().HasTable(&ChannelTable{}) {
		//创建通道信息表
		CreateTable(&ChannelTable{}, "通道信息表")
	}
}

//创建表格，如果表格不存在，参数二是表名描述信息，比如：用户信息表
func CreateTable(dst interface{}, tableComment string) {
	tableName := GetTableName(dst)
	switch DBType {
	case "mysql":
		//ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE = utf8_general_ci
		if err := gormDB.Set("gorm:table_options", fmt.Sprintf("ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='%s'", tableComment)).Migrator().CreateTable(dst); err != nil {
			logs.PanicLogger.Panicln(fmt.Sprintf("create %s table failed: %s", tableName, err))
		}
	case "sqlite3":
		if err := gormDB.Migrator().CreateTable(dst); err != nil {
			logs.PanicLogger.Panicln(fmt.Sprintf("create %s table failed: %s", tableName, err))
		}
	}

	logs.BeeLogger.Emergency("create %s table success", tableName)
}

//通过数据的指针来创建记录，目前支持批量插入操作
/*示例1：使用结构体数组指针进行批量插入操作，当然也支持直接使用结构体指针进行插入操作
var users = []User{
	{
		Name: "王意德",
		Age:  18,
		Addr: "大田埂",
	},
	{
		Name: "德意王",
		Age:  18,
		Addr: "李庄",
	},
	{
		Name: "意德王",
		Age:  16,
		Addr: "李庄",
	},
}
Create(&User{}, &users, false)
*/
/*示例2：使用map数组指针进行批量插入操作，当然也支持直接使用map指针进行插入操作
/*示例：var users = []map[string]interface{}{
	{"Name": "王意德", "Age": 18, "Addr": "大田埂", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
	{"Name": "德意王", "Age": 18, "Addr": "李庄", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
	{"Name": "意德王", "Age": 16, "Addr": "李庄", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
}
Create(&User{}, &users, true)
*/
func Create(table, value interface{}, isSliceMap bool) error {
	switch isSliceMap {
	case true:
		//使用map数组进行批量插入
		if err := gormDB.Model(table).Create(value).Error; err != nil {
			return err
		}
	default:
		//使用struct数组进行批量插入
		if err := gormDB.Create(value).Error; err != nil {
			return err
		}
	}

	return nil
}

//解析获得用户要操作的表名
func GetTableName(value interface{}) (tableName string) {
	if name, ok := value.(string); ok {
		tableName = name
	} else {
		//fmt.Println("类型打印：", reflect.TypeOf(gormDB.Migrator()))
		switch mg := gormDB.Migrator().(type) {
		case sqlite.Migrator:
			mg.RunWithValue(value, func(statement *gorm.Statement) error {
				tableName = statement.Table
				return nil
			})
		case mysql.Migrator:
			mg.RunWithValue(value, func(statement *gorm.Statement) error {
				tableName = statement.Table
				return nil
			})
		}
	}

	return
}

//更新操作：主要用于更新指定列内容，当然主键为空则更新所有
//更新多列，更新某个主键对应的数据，主键不为空更新单条记录数据，主键为空更新所有记录数据
//示例1：有主键更新一条记录：UpdatesMultipleColumns(&User{Name: "王意德"}, map[string]interface{}{"Age": 16, "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")}, true)
//示例2：无主键更新所有记录：UpdatesMultipleColumns(&User{}, map[string]interface{}{"Age": 16, "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")}, false)
func UpdatesMultipleColumns(value interface{}, mapData map[string]interface{}, havePrimaryKey bool) error {
	switch havePrimaryKey {
	case true:
		//有主键，更新单条记录数据
		if err := gormDB.Model(value).Where("channelID = ?", value.(ChannelTable).ChannelID).Updates(mapData).Error; err != nil {
			return err
		}
	case false:
		//无主键，更新所有记录数据
		if err := gormDB.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(value).Updates(mapData).Error; err != nil {
			return err
		}
	}

	return nil
}

//批量更新，参数一是表名，参数二是更新内容，参数三是条件，后面参数是参数三数据
//说明：参数四map不要传主键。经测试发现map若含有主键，如果数据库中符合条件的只有一条记录且数据库不存在此主键值会更新成功，其它都会失败
//示例：批量更新符合条件的记录：BatchUpdate("user",map[string]interface{}{"UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},"Age IN ?", []int64{18})
//示例：若无需按照条件更新，则使用：BatchUpdate("user",map[string]interface{}{"UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},"1 = 1")
func BatchUpdate(tableName string, mapData map[string]interface{}, query string, args ...interface{}) error {
	if err := gormDB.Table(tableName).Where(query, args...).Updates(mapData).Error; err != nil {
		return err
	}

	return nil
}

//批量插入更新：Gorm会生成insert into语句执行插入更新操作
//适用于执行更新操作时更新所有字段，参数一可以是结构体指针，也可以是结构体数组指针
//建议不要直接传结构体，因为这样save执行更新操作时会修改CreatedAt为零值，老版没这个问题，估计是新版的问题。若无需维护这个字段则都可以
//说明：出现上面问题原因是创建时间和更新时间由Gorm维护
/*示例：
var users = []User{
	{
		Name: "王意德",
		Age:  18,
		Addr: "大田埂",
	},
	{
		Name: "德意王",
		Age:  18,
		Addr: "李庄",
	},
	{
		Name: "意德王",
		Age:  16,
		Addr: "李庄",
	},
}
SaveStruct(&users)
*/
func SaveStruct(value interface{}) error {
	if err := gormDB.Save(value).Error; err != nil {
		return err
	}

	return nil
}

//批量插入更新：Gorm会生成insert into语句执行插入更新操作
//适用于执行更新操作时更新部分字段，参数一是结构体指针，参数二可以是map指针，也可以是map数组指针
//参数二使用map经测试发现Gorm只会执行更新语句，不会自动根据条件执行插入操作，慎用，建议使用map数组指针，缺点是创建时间和更新时间不好维护
/*示例：var users = []map[string]interface{}{
	{"Name": "王意德", "Age": 18, "Addr": "大田埂", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
	{"Name": "德意王", "Age": 18, "Addr": "李庄", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
	{"Name": "意德王", "Age": 16, "Addr": "李庄", "CreatedAt": time.Now().Format("2006-01-02 15:04:05"), "UpdatedAt": time.Now().Format("2006-01-02 15:04:05")},
}
SaveMap(&User{}, &users)
*/
func SaveMap(value interface{}, sliceMapData interface{}) error {
	if err := gormDB.Model(value).Save(sliceMapData).Error; err != nil {
		return err
	}

	return nil
}

//执行原生SQL
func Exec(sql string) error {
	if err := gormDB.Exec(sql).Error; err != nil {
		return err
	}

	return nil
}

//查询操作
//使用内联条件查询符合的记录，参数一是结构体数组指针，参数二是表名，后面参数是查询条件
//示例1：Find(&users, "Age = ? AND Addr= ?", 18, "大田埂")
//示例2：Find(&users, map[string]interface{}{"Age": 18, "Addr": "大田埂"})
func Find(dest interface{}, conds ...interface{}) error {
	if err := gormDB.Find(dest, conds...).Error; err != nil {
		return err
	}

	return nil
}

//使用内联条件获取第一条匹配的记录，若无主键则按第一个字段排序返回一个查询的结果。返回值：true表示找到一个符合条件的记录，false没找到
//参数一是结构体指针，后面参数是可选查询条件
//示例1：First(&User{}, "Age = ? AND Addr= ?", 18, "大田埂")
//示例2：First(&User{}, map[string]interface{}{"Age": 18, "Addr": "大田埂"})
func First(dest interface{}, conds ...interface{}) error {
	if err := gormDB.First(dest, conds...).Error; err != nil {
		logs.BeeLogger.Error("error first eligible record in the %s table: %s", GetTableName(dest), err)
		//errors.Is(err, gorm.ErrRecordNotFound)
		//err.Error() == "record not found"
		return err
	}
	return nil
}

//用于获取匹配的记录数：参数一是结构体指针，参数二是统计条件，后面所传参数是参数二数据
//示例1：查询某个表所有记录数量：Count(&User{}, "")
//示例2：查询某个表符合条件的记录数量：Count(&User{}, "name = ?", "王意德")
func Count(value interface{}, query string, args ...interface{}) (count int64) {
	tableName := GetTableName(value)
	if query == "" {
		if err := gormDB.Table(tableName).Count(&count).Error; err != nil {
			logs.BeeLogger.Error("error get all records counts in the %s table: %s", tableName, err)
		}
	} else {
		if err := gormDB.Model(value).Where(query, args...).Count(&count).Error; err != nil {
			logs.BeeLogger.Error("error get eligible records count in the %s table: %s", tableName, err)
		}
	}

	return
}

//带条件的分页查询：参数一是结构体数组指针，参数二是表名，参数三是limit欲查询的数量，参数四是offset跳过的数量，后面是可选分页查询条件
//示例1：分页查询符合条件的记录：PagingQuery(&users, 100, 10, "Name = ? AND Age = ?", "王意德", 18)
//示例2：分页查询记录：PagingQuery(&users, 100, 10)
func PagingQuery(dest interface{}, limit, offset int, conds ...interface{}) error {
	if err := gormDB.Limit(limit).Offset(offset).Find(dest, conds...).Error; err != nil {
		return err
	}

	return nil
}

//删除操作：参数一是结构体指针，参数二说明参数一是否含有主键，参数三是删除条件，后面所传参数是参数三数据
//示例1：主键不存在，批量删除表格中所有记录: BatchDelete(&User{}, false, "")
//示例2：主键存在，直接删除该主键对应的记录：BatchDelete(&User{Name:"王意德"}, true, "")
//示例3：按条件删除符合条件的记录，主键存在删除主键对应的记录：BatchDelete(&User{Name:"王意德"}, true, "Age = ?",18)
//示例4：按条件删除符合条件的记录，主键不存在批量删除符合条件的记录：BatchDelete(&User{}, false, "Age = ?",18)
func BatchDelete(value interface{}, havePrimaryKey bool, query string, args ...interface{}) error {
	switch query {
	case "":
		//批量删除记录，主键存在只删除一条记录，否则删除表格所有记录
		switch havePrimaryKey {
		case true:
			//主键存在，直接删除该主键对应的记录
			if err := gormDB.Delete(value).Error; err != nil {
				return err
			}
		case false:
			//主键不存在，批量删除表格中所有记录
			if err := gormDB.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(value).Error; err != nil {
				return err
			}
		}
	default:
		//删除符合条件的记录，主键存在则删除主键对应的记录，否则批量删除所有符合条件的记录
		if err := gormDB.Where(query, args...).Delete(value).Error; err != nil {
			return err
		}
	}

	return nil
}

//下面示例中DeviceID是非自增长主键
//MySQLInsertIntoForChannelTable ---> INSERT INTO `channel` (`DeviceID`,`CreatedAt`,`Name`,`Status`,`Model`,`DASID`,`UpdatedAt`) VALUES %s ON DUPLICATE KEY UPDATE `Name`=VALUES(`Name`),`Status`=VALUES(`Status`),`Model`=VALUES(`Model`),`DASID`=VALUES(`DASID`),`UpdatedAt`=VALUES(`UpdatedAt`)
//SQLite3InsertIntoForChannelTable ---> INSERT INTO `channel` (`DeviceID`,`CreatedAt`,`Name`,`Status`,`Model`,`DASID`,`UpdatedAt`) VALUES %s ON CONFLICT (`DeviceID`) DO UPDATE SET `Name`=`excluded`.`Name`,`Status`=`excluded`.`Status`,`Model`=`excluded`.`Model`,`DASID`=`excluded`.`DASID`,`UpdatedAt`=`excluded`.`UpdatedAt`
//构造批量插入更新SQL语句：参数一是插入字段数组，参数二是更新字段数据，两个数组的完整字段构成执行插入操作的所有字段，两个数组不能有重复字段，否则构造的SQL执行会报错
//参数一数组第一个字段必须是非自增长字段或者唯一索引，作用是SQL语句会根据此字段判断执行插入操作还是更新操作。注意！！！注意！！！注意！！！
//参数三是表名，参数四是要操作的数据库类型，目前仅支持mysql和sqlite3。
func GetInsertIntoSQL(insertFields, updateFields []string, tableName, dbType string) (sqlString string) {
	if len(insertFields) == 0 {
		return
	}

	//处理插入字段数组
	sqlString = fmt.Sprintf("INSERT INTO `%s` ", tableName)
	for index, field := range insertFields {
		if index == 0 {
			sqlString += fmt.Sprintf("(`%s`", field)
		} else {
			sqlString += fmt.Sprintf(",`%s`", field)
		}
	}

	for _, updateField := range updateFields {
		sqlString += fmt.Sprintf(",`%s`", updateField)
	}

	sqlString += ") VALUES %s "

	//处理更新字段数组，注意不同数据库的插入更新语句有所不同
	if len(updateFields) != 0 {
		switch dbType {
		case "mysql":
			sqlString += "ON DUPLICATE KEY UPDATE "
			for index, field := range updateFields {
				if index == 0 {
					sqlString += fmt.Sprintf("`%s`=VALUES(`%s`)", field, field)
				} else {
					sqlString += fmt.Sprintf(",`%s`=VALUES(`%s`)", field, field)
				}
			}
		case "sqlite3":
			//注意sqlite3构造插入更新语句时 ON CONFLICT (`filed`) DO UPDATE SET里的filed字段必须是唯一索引(包含非自增长主键)
			sqlString += fmt.Sprintf("ON CONFLICT (`%s`) DO UPDATE SET ", insertFields[0])
			for index, field := range updateFields {
				if index == 0 {
					sqlString += fmt.Sprintf("`%s`=`excluded`.`%s`", field, field)
				} else {
					sqlString += fmt.Sprintf(",`%s`=`excluded`.`%s`", field, field)
				}
			}
		}
	}

	return
}
